<?php

class asynchronous_Object_CronjobRun {

	/* a message is an action posted by another back end where some processing needs to be done. 
	The message gives the detail of which back end should be called and transfer parameters so the back end can perform the action requested
	*/
    protected $_runId;
    protected $_jobId;    
    protected $_runAfter;
    protected $_ranAt;

    protected $_data;
    
    protected $_jobObject; 
	
	/* definition of the status table */
	static protected $_dbCronjobRunTableName = 'cron_jobRun';
	
	//Identifier for the run
	static protected $_dbCronjobRunRunIdCol = 'runId';
	//Identifier of the job that need to be run
	static protected $_dbCronjobRunJobIdCol = 'jobId';
	//After what UNIX time the job should be run
	static protected $_dbCronjobRunRunAfterCol = 'runAfter';
	//When it was effectively ran
	static protected $_dbCronjobRunRanAtCol = 'ranAt';
	//Details of the output of the job
	static protected $_dbCronjobRunDataCol = 'data';
	
	static protected $_cronjobDataMessageElem = 'message';
	
	public function __construct($runId,$data = array()) {
		if(empty($data)) {
			//We validate that the id is numeric
			$validator = new Zend_Validate_Digits();
			if(!$validator->isValid($runId)) {
				require_once('asynchronous/objects/Exception.php');
				throw new Asynchronous_Object_Exception('Invalid run identifier',1); 	  	
			}
	  
			$db = self::_getDb();	
			$select = $db->select()->from(self::$_dbCronjobRunTableName);
			$select->where($db->quoteInto(self::$_dbCronjobRunRunIdCol ." = ?", $runId));
			$res = $db->fetchAll($select->__toString());
			
			if(empty($res)) {
				require_once('asynchronous/objects/Exception.php');
				throw new Asynchronous_Object_Exception('cronjobrun not found',2); 
			}
			$data = $res[0];			
		}


		//we fill the object with the data
	    $this->_runId = $data[self::$_dbCronjobRunRunIdCol];
	    $this->_jobId = $data[self::$_dbCronjobRunJobIdCol];	    
	    $this->_runAfter = $data[self::$_dbCronjobRunRunAfterCol];
	    $this->_ranAt = $data[self::$_dbCronjobRunRanAtCol];
	    
	    //other fields are stored in the data object
		if(!empty($data[self::$_dbCronjobRunDataCol])) {
			$decodedBlob = self::decodeJobRunData($data[self::$_dbCronjobRunDataCol]);	    
			$this->_data = $decodedBlob;
		}
	} //end __construct

	
	public function getJobObject() {
		if(!empty($this->_jobObject)) return $this->_jobObject;
		require_once('asynchronous/objects/cronjob.php');
		$this->_jobObject = new asynchronous_Object_Cronjob($this->getJobId());
		return $this->_jobObject;
	}
	
	//Object model when returned in the API
	public function asArray() {
		$results = array();
	
		$results['runId'] = $this->getRunId();
		$results['jobId'] = $this->_jobId;
		$results['runAfterUnixTimestamp'] = $this->_runAfter;
		$results['runAfterISO8601'] = date('c',$this->_runAfter);			
		if(!empty($this->_ranAt)) {
			$results['ranAtUnixTimestamp'] = $this->_ranAt;
			$results['ranAtISO8601'] = date('c',$this->_ranAt);			
		}
		if(!empty($this->_data)) $results['data'] = $this->_data; 
		
		
		return $results;
	}
	
	public function markJobAsRan() {
			$db = self::_getDb();	
			$data = array(
				self::$_dbCronjobRunRanAtCol => time()
			);
			$n = $db->update(self::$_dbCronjobRunTableName, $data, $db->quoteInto(self::$_dbCronjobRunRunIdCol ." = ?", $this->getRunId()));
			if($n != 1) Zend_Registry::get('dt_log')->log('Issue updating ran job '.$n.' rows updated',Zend_Log::ERR);
	}
	
	public function setOutputMessage($message) {
			$db = self::_getDb();	
			$messageDetails = unserialize($message);
			
			$serializedData = self::encodeJobRunData(array(self::$_cronjobDataMessageElem => $messageDetails));
			
			$data = array(
				self::$_dbCronjobRunDataCol => $serializedData
			);
			$n = $db->update(self::$_dbCronjobRunTableName, $data, $db->quoteInto(self::$_dbCronjobRunRunIdCol ." = ?", $this->getRunId()));		
	}
	
	
	static public function getPublishedJobNotRunned() {
			$db = self::_getDb();	
			$select = $db->select()->from(self::$_dbCronjobRunTableName,self::$_dbCronjobRunJobIdCol);
			$select->where(self::$_dbCronjobRunRanAtCol ." is NULL");
			
			$res = $db->fetchAll($select->__toString());
			
			$output = array();
			if(empty($res)) return $output;
			
			foreach($res as $num => $data) {
				$output[] = $data[self::$_dbCronjobRunJobIdCol];
			}
			return $output;
			
	} //end getPublishedJobNotRunned



	//return the last jobrun for a particular job
	static public function getJobLastRun($jobId) {
			$db = self::_getDb();	
			$select = $db->select()->from(self::$_dbCronjobRunTableName);
			$select->where($db->quoteInto(self::$_dbCronjobRunJobIdCol ." = ?", $jobId))
					->where(self::$_dbCronjobRunRanAtCol ." is NOT NULL")
					->limit(1)
					->order(self::$_dbCronjobRunRunIdCol.' DESC');
			$res = $db->fetchAll($select->__toString());
			if(empty($res)) return '';
			
			return new asynchronous_Object_CronjobRun('',$res[0]);		
					
	} //getJobLastRun
	
	/*
	@param object asynchronous_Object_Cronjob
	@return runId
	*/ 
	static public function publishJob(asynchronous_Object_Cronjob $jobObject) {
		
		$jobId = $jobObject->getId();
		$runAfter = $jobObject->getNextExecutionTimestamp();
		
		$toBesaved = array(
			self::$_dbCronjobRunJobIdCol => $jobId,
			self::$_dbCronjobRunRunAfterCol => $runAfter
		);
	  	//and we put it in the database
	  	$db = self::_getDb();
	  	
	    $db->insert(self::$_dbCronjobRunTableName, $toBesaved);
	    
	    //we get the last inserted Id
	    $runId = $db->lastInsertId();
		
		return $runId;

	
	}	

	public function getRunId() {
		return $this->_runId;	
	}

	public function getJobId() {
		return $this->_jobId;	
	}

	
	static public function getJobToRun() {
			$db = self::_getDb();	
			$select = $db->select()->from(self::$_dbCronjobRunTableName);
			$select->where($db->quoteInto(self::$_dbCronjobRunRunAfterCol ." < ?", time()))->limit(20);
			$select->where(self::$_dbCronjobRunRanAtCol ." is NULL");
			$res = $db->fetchAll($select->__toString());
			if(empty($res)) return;
			
			//we have a set of up to 20 results, we select one randomly (can t trust db to have common way to order by rand)
			$nbRes = count($res);
			if($nbRes == 1) $resCount = 0;
			else $resCount = rand  ( 0 , $nbRes-1);
			return new asynchronous_Object_CronjobRun('',$res[$resCount]);
			
	}


	//the message is stored as a base64 gzdefalted serialized PHP
	static public function decodeJobRunData($data) {
		$decoded = @unserialize(gzinflate(base64_decode($data)));
		if(empty($decoded)) {
			require_once('asynchronous/objects/Exception.php');
			throw new Asynchronous_Object_Exception('An issue occured while decoding message',16);
		}  
		return  $decoded;	
	}

	static public function encodeJobRunData($data) {
	  	if(!is_array($data)) {
	  		require_once('asynchronous/objects/Exception.php');
	  		throw new Asynchronous_Object_Exception('input parameter is not an array',12); 
	  	}
	    $encoded = @base64_encode(gzdeflate(serialize($data),9));
	    if(empty($encoded)) {
	    	require_once('asynchronous/objects/Exception.php');
	    	throw new Asynchronous_Object_Exception('An issue occured while encoding message',17);
	    } 
	    return $encoded;   	
	}	
	
	static protected function _getDb() {
	  	return Zend_Registry::get('dt_dbs')->getDb('asynchronous','cronjob','key');	
	}
  
} //end class